using System;
using System.Collections;
using System.ComponentModel;
using System.Reflection;
using Edge.Common.ComponentModel;
using Edge.Common.ComponentModel;

namespace Edge.Common.Utils
{
    [AttributeUsage(AttributeTargets.Field)]
    public class CanCopyAttribute : Attribute
    {
        private bool canCopy = true;

        public CanCopyAttribute(bool canCopy)
        {
            this.canCopy = canCopy;
        }

        public bool CanCopy
        {
            get { return canCopy; }
            set { canCopy = value; }
        }
    }

    /// <summary>
    /// Summary description for ReflectionUtils.
    /// </summary>
    public class ReflectionUtils
    {

        private const string GETTER_PREFIX = "get_";
        protected ReflectionUtils()
        {
        }

        #region Properties utils

        public static object GetNonPublicPropertyValue(object o, string name)
        {
            PropertyInfo pi = o.GetType().GetProperty(name, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
            if (pi == null)
                throw new ArgumentException("property does not exist: " + name);
            return pi.GetValue(o, null);
        }

        public static object GetNonPublicFileldValue(object o, string name)
        {
            FieldInfo fi = o.GetType().GetField(name, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
            if (fi == null)
                throw new ArgumentException("property does not exist: " + name);
            return fi.GetValue(o);
        }



        public static object GetPublicPropertyValue(object o, string name)
        {
            return GetPublicPropertyValue(o, name, null);
        }

        public static object GetPublicPropertyValue(object o, string name, object[] index)
        {
            MemberInfo[] members = o.GetType().GetMember(name, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.GetProperty);
            if (members == null || members.Length == 0 || members[0].MemberType != MemberTypes.Property)
                throw new ArgumentException("property does not exist: " + name + " in type " + o.GetType());
            return ((PropertyInfo)members[0]).GetValue(o, index);
        }

        public static object Evalute(object o, string path)
        {
            if (o == null)
                throw new ArgumentException("o");
            if (path == null)
                throw new ArgumentException("path");

            string[] pathElements = path.Split('.');
            object curr = o;
            foreach (string pathElement in pathElements)
            {
                if (curr == null)
                    throw new Exception("could not evalute path '" + pathElement +
                                        "' besause previouse preperty returned null");
                curr = GetPublicPropertyValue(curr, pathElement);
            }

            return curr;
        }

        public static void SetValue(object o, string propertyName, object v)
        {
            SetValue(o, propertyName, v, 0);
        }

        public static void SetValue(object o, string propertyName, object v, BindingFlags flags)
        {
            PropertyInfo pi = o.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | flags);

            if (pi == null)
                throw new ApplicationException("property " + propertyName + "does not exists");

            pi.SetValue(o, v, null);

        }

        public static void SetValue(Type type, object o, string propertyName, object v)
        {
            PropertyInfo pi = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);

            if (pi == null)
                throw new ApplicationException("property " + propertyName + "does not exists");

            pi.SetValue(o, v, null);

        }

        public static string PropertyNameFromGetter(MethodInfo mi)
        {
            if (!mi.Name.StartsWith(GETTER_PREFIX) || mi.Name.Length == GETTER_PREFIX.Length)
                throw new ArgumentException(mi.Name + " is not property getter");

            return mi.Name.Substring(GETTER_PREFIX.Length);
        }


        public static void SetFieldValue(object o, string propertyName, object v)
        {
            SetFieldValue(o, propertyName, v, 0);
        }

        public static void SetFieldValue(object o, string filedName, object v, BindingFlags flags)
        {
            FieldInfo fieldInfo = o.GetType().GetField(filedName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | flags);

            if (fieldInfo == null)
                throw new ApplicationException("field " + filedName + "does not exists");

            fieldInfo.SetValue(o, v);

        }

        #endregion

        #region Cast utils

        public static T Cast<T>(object value, T nullValue)
        {
            return value == null ? nullValue : (T)value;
        }

        #endregion

        #region Attributes utils

        public static T GetFirstAttribure<T>(ICustomAttributeProvider provider, bool inherit)
            where T : class
        {
            object[] attrs = provider.GetCustomAttributes(typeof(T), true);
            if (!CollectionsUtils.IsEmpty((ICollection)attrs))
                return (T)attrs[0];
            return null;
        }

        public static T GetFirstAttribure<T>(ICustomAttributeProvider provider)
            where T : class
        {
            return GetFirstAttribure<T>(provider, true);
        }

        protected static bool GetCanCopy(ICustomAttributeProvider fi)
        {
            CanCopyAttribute canCopyAttribute =
                GetFirstAttribure<CanCopyAttribute>(fi, true);
            if (canCopyAttribute != null)
                return canCopyAttribute.CanCopy;
            return true;
        }

        #endregion

        #region Copy fields

        /// <summary>
        /// copy all protected and private fileds values
        /// </summary>
        /// <param name="source"></param>
        /// <param name="target"></param>
        /// <param name="copyNoClonable"></param>
        public static void CopyNoPuplicFields(object source, object target, bool copyNoClonable)
        {
            CopyFields(source, target, copyNoClonable, BindingFlags.NonPublic);
        }

        /// <summary>
        /// copy all fileds values
        /// </summary>
        /// <param name="source"></param>
        /// <param name="target"></param>
        /// <param name="copyNoClonable"></param>
        public static void CopyAllFields(object source, object target, bool copyNoClonable)
        {
            CopyFields(source, target, copyNoClonable, BindingFlags.NonPublic | BindingFlags.Public);
        }

        /// <summary>
        /// copy all protected and private fileds values
        /// </summary>
        /// <param name="source"></param>
        /// <param name="target"></param>
        /// <param name="copyNoClonable"></param>
        public static void CopyFields(object source, object target, bool copyNoClonable, BindingFlags falgs)
        {
            if (source == null)
                throw new ArgumentException("source");
            if (target == null)
                throw new ArgumentException("target");
            if (source.GetType() != target.GetType())
                throw new ArgumentException("source and target mast be same types");

            FieldInfo[] fields = source.GetType().GetFields(BindingFlags.Instance | falgs);
            foreach (FieldInfo fieldInfo in fields)
            {
                if (!GetCanCopy(fieldInfo))
                {
                    continue;
                }

                if (fieldInfo.FieldType.IsValueType)
                {
                    // valuetype
                    object value = fieldInfo.GetValue(source);
                    fieldInfo.SetValue(target, value);
                }
                else
                {
                    // if implemented ICloneable
                    if (typeof(ICloneable).IsAssignableFrom(fieldInfo.FieldType))
                    {
                        ICloneable value = (ICloneable)fieldInfo.GetValue(source);
                        fieldInfo.SetValue(target, value != null ? value.Clone() : null);
                    }
                    else // nonclonable object
                    {
                        if (copyNoClonable)
                        {
                            object value = fieldInfo.GetValue(source);
                            fieldInfo.SetValue(target, value);
                        }
                    }
                }
            }
        }

        #endregion

        #region Equals

        public static bool EqualsAllFields(object obj1, object obj2)
        {
            if (obj1 == obj2)
                return true;

            if (obj1 == null || obj2 == null)
                return false;

            if (obj2.GetType() != obj2.GetType())
                return false;

            bool ret = true;

            FieldInfo[] fields = obj1.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            foreach (FieldInfo fieldInfo in fields)
            {
                object obj1Value = fieldInfo.GetValue(obj1);
                object obj2Value = fieldInfo.GetValue(obj2);
                if (!Object.Equals(obj1Value, obj2Value))
                {
                    ret = false;
                    break;
                }
            }

            return ret;
        }

        #endregion

        #region Invoke utils

        public static object InvokeStatic(Type type, string methodName, params object[] args)
        {
            if (methodName == null)
                throw new ArgumentException("methodName");

            MethodInfo mi = type.GetMethod(methodName,
                                           BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public);

            if (args == null)
                args = new object[] { };

            return mi.Invoke(null, args);
        }

        public static object Invoke(object self, string methodName, params object[] args)
        {
            if (self == null)
                throw new ArgumentException("self");

            if (methodName == null)
                throw new ArgumentException("methodName");


            MethodInfo mi = self.GetType().GetMethod(methodName,
                                           BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public);

            if (args == null)
                args = new object[] { };

            if (mi == null)
                throw new ArgumentException("method " + methodName + " does not exists");

            return mi.Invoke(self, args);
        }

        public static object Invoke(object self, string methodName, object[] args, Type[] paramTypes)
        {
            if (self == null)
                throw new ArgumentException("self");

            if (methodName == null)
                throw new ArgumentException("methodName");


            MethodInfo mi = self.GetType().GetMethod(methodName,
                                           paramTypes);

            if (args == null)
                args = new object[] { };

            return mi.Invoke(self, args);
        }

        #endregion

        /// <summary>
        /// work for generics also
        /// example with generic:
        /// 
        /// IsInheritOrSame(typeof(IList<>), typeof(List<int>)); //  returned true
        /// 
        /// </summary>
        /// <param name="potentialBase"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsInheritOrSame(Type potentialBase, Type type)
        {
            if (potentialBase == null)
                throw new ArgumentException("potentialBase");
            if (type == null)
                throw new ArgumentException("type");

            if (potentialBase.IsGenericTypeDefinition)
            {
                foreach (Type ifcType in type.GetInterfaces())
                {
                    if (ifcType.IsGenericType && ifcType.GetGenericTypeDefinition() == potentialBase)
                    {
                        return true;
                    }
                }

                Type probe = type;
                while (probe != typeof(object))
                {
                    if (probe.IsGenericType && probe.GetGenericTypeDefinition() == potentialBase)
                        return true;

                    probe = probe.BaseType;
                }
            }

            return potentialBase.IsAssignableFrom(type);
        }

        public static Type GetGetenricParametr(Type type, Type genericType)
        {
            foreach (Type ifcType in type.GetInterfaces())
            {
                if (ifcType.IsGenericType && ifcType.GetGenericTypeDefinition() == genericType)
                {
                    return ifcType.GetGenericArguments()[0];
                }
            }

            while (type != typeof(object))
            {
                if (type.IsGenericType && type.GetGenericTypeDefinition() == genericType)
                {
                    return type.GetGenericArguments()[0];
                }

                type = type.BaseType;
            }

            return null;

        }

        public static string GetDescriptionAttribute(Type type)
        {
            object[] attributes = type.GetCustomAttributes(typeof(DescriptionAttribute), false);
            return attributes.Length > 0 ? ((DescriptionAttribute)(attributes[0])).Description : null;
        }

        public static bool GetLogAttribute(Type type)
        {
            object[] attributes = type.GetCustomAttributes(typeof(LogAttribute), false);
            return attributes.Length > 0 ? ((LogAttribute)(attributes[0])).NeedLog : false;
        }
    }
}
